home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ham Radio 2000 #2
/
Ham Radio 2000 - Volume 2.iso
/
HAMV2
/
MISC
/
DTMFF110
/
SETUPSUB.C
< prev
Wrap
C/C++ Source or Header
|
1997-08-05
|
21KB
|
700 lines
/*
* Routines for setting up the parameters/buffers/display
*
* Copyright (C) 1995 Philip VanBaren (C) 1996 Emil LAURENTIU
*/
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <ctype.h>
#include "freq.h"
#include "extern.h"
#include "display.h"
int fftlen = 1024; /* Number of points for FFT */
long SampleRate = 20000L; /* A/D sampling rate */
int logfreq = 0; /* Flag set to 1 for log-based frequency
* scale */
float ys = 0.1; /* Flag set for max of y-axis */
float threshold_level = 0.05; /* Treshold level used in DTMF &
* CTCSS modes */
int dtmf_delay = 80; /* 1/3 Length of generated DTMF tones in ms */
int Soundcard = SC_DEF; /* Soundcard number (as defined in
* freq.h) */
int sb_irq = SB_IRQ; /* IRQ used by the Soundblaster card */
int sb_dma = SB_DMA; /* DMA channel used by the Soundblaster card */
int sb_addr = SB_ADDR;/* I/O address of the Soundblaster card */
float maxfreq;
double alpha; /* Gaussian window parameter */
struct rgb background = {0, 0, 20},
warn = {20, 0, 0},
graph = {30, 35, 60},
tick = {40, 40, 40},
label = {0, 63, 0},
border = {63, 0, 0},
text = {63, 63, 0},
darkhl = {20, 20, 20},
lighthl = {63, 63, 63};
/* Center frequencies for equalizer-mode display */
double center_freq[] = {4, 5, 6.3, 8, 10, 12.5, 16, 20, 25, 31.5, 40, 50,
63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630,
800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000,
6300, 8000, 10000, 12500, 16000, 20000, 25000};
#define EQUALIZER_BINS (sizeof(center_freq)/sizeof(center_freq[0]))
/* Soundcard function pointers */
void ( *init_soundcard ) ( void ) = NULL;
void ( *reset_soundcard ) ( void ) = NULL;
void ( *halt_soundcard ) ( void ) = NULL;
void ( *cleanup_soundcard ) ( void ) = NULL;
void ( *recordblock ) ( void far * ) = NULL;
void ( *set_mixer ) ( int, int ) = NULL;
int sample_size; /* Bits per sample (8 or 16) */
int mixers; /* Mixers available (1) or not (0) */
#include <alloc.h>
void far *
aligned_malloc( long len )
{
void far *ptr;
unsigned int far *orig_ptr;
unsigned seg, orig_seg, orig_off;
long lin_addr;
/* Allocate double the required space */
ptr = farmalloc( len * 2 + 32 );
if ( ptr != NULL )
{
/* Compute a new pointer to the buffer such that the next "len" bytes */
/* do not cross a page boundary, and the offset is zero */
/* (as required by DMA transfers) */
orig_off = FP_OFF( ptr );
orig_seg = FP_SEG( ptr );
/* reserve 4 bytes for the original pointer */
lin_addr = ( orig_seg * 16L ) + orig_off + 4L;
if ( ( lin_addr & 0xF0000L ) != ( ( lin_addr + len - 1 ) & 0xF0000L ) )
lin_addr = ( lin_addr + len - 1 ) & 0xF0000L;
else
lin_addr = ( lin_addr + 15 ) & 0xFFFF0L;
seg = ( unsigned int ) ( lin_addr / 16 );
orig_ptr = ( unsigned far * ) MK_FP( seg - 1, 0x000C );
orig_ptr[0] = orig_off;
orig_ptr[1] = orig_seg;
ptr = MK_FP( seg, 0 );
/*
* printf("Original: %04x:%04x, New: %04x:%04x, Linear: %05lx\n",
* orig_seg,orig_off,FP_SEG(ptr),FP_OFF(ptr),lin_addr);
*/
}
return ptr;
}
void
aligned_free( void far * ptr )
{
if ( ptr != NULL )
{
unsigned far *old_ptr = ( unsigned far * ) MK_FP( FP_SEG( ptr ) - 1, 0x000c );
/*
* printf("Freeing: %04x:%04x, Ptr: %04x:%04x\n",
* FP_SEG(old_ptr),FP_OFF(old_ptr),FP_SEG(ptr),FP_OFF(ptr));
*/
farfree( MK_FP( old_ptr[1], old_ptr[0] ) );
}
}
/*
* Parse the ini file, if it exists
*/
void
parse_ini_file( void )
{
int i;
FILE *fp;
char buffer[100];
if ( ( fp = fopen( ini_file, "r" ) ) != NULL )
{
while ( !feof( fp ) )
{
fgets( buffer, sizeof( buffer ), fp );
for ( i = 0; ( buffer[i] != 0 ) && ( buffer[i] != ':' ); i++ )
buffer[i] = toupper( buffer[i] );
if ( strncmp( buffer, "SOUNDCARD:", 10 ) == 0 )
Soundcard = parsecardname( buffer + 10 );
sscanf( buffer, "SAMPLE RATE:%ld", &SampleRate );
sscanf( buffer, "FFT LENGTH:%d", &fftlen );
sscanf( buffer, "LOG FREQ SCALE:%d", &logfreq );
sscanf( buffer, "MAX AMP:%f", &ys );
sscanf( buffer, "BASE FREQUENCY:%f", &freq_base );
sscanf( buffer, "FREQUENCY FACTOR:%f", &freq_scalefactor );
sscanf( buffer, "DTMF & CTCSS THRESHOLD LEVEL:%f", &threshold_level );
sscanf( buffer, "DTMF DELAY (100 - 1000 MS):%d", &dtmf_delay );
sscanf( buffer, "CTCSS ACTIVE:%lx,%lx", &ctcss_act1, &ctcss_act2 );
sscanf( buffer, "BACKGROUND COLOR:%d,%d,%d", &background.red, &background.green, &background.blue );
sscanf( buffer, "CLIPPING WARNING COLOR:%d,%d,%d", &warn.red, &warn.green, &warn.blue );
sscanf( buffer, "GRAPH COLOR:%d,%d,%d", &graph.red, &graph.green, &graph.blue );
sscanf( buffer, "TICK MARK COLOR:%d,%d,%d", &tick.red, &tick.green, &tick.blue );
sscanf( buffer, "AXIS LABEL COLOR:%d,%d,%d", &label.red, &label.green, &label.blue );
sscanf( buffer, "BORDER COLOR:%d,%d,%d", &border.red, &border.green, &border.blue );
sscanf( buffer, "TEXT COLOR:%d,%d,%d", &text.red, &text.green, &text.blue );
sscanf( buffer, "CURSOR UPPER COLOR:%d,%d,%d", &darkhl.red, &darkhl.green, &darkhl.blue );
sscanf( buffer, "CURSOR LOWER COLOR:%d,%d,%d", &lighthl.red, &lighthl.green, &lighthl.blue );
}
fclose( fp );
dtmf_delay /= 3;
}
}
void
setnormalpalette( void )
{
draw_setpalette( 0, background.red, background.green, background.blue );
draw_setpalette( LABEL_COLOR, label.red, label.green, label.blue );
draw_setpalette( BORDER_COLOR, border.red, border.green, border.blue );
draw_setpalette( TEXT_COLOR, text.red, text.green, text.blue );
draw_setpalette( GRAPH_COLOR, graph.red, graph.green, graph.blue );
draw_setpalette( DARK_HIGHLIGHT, darkhl.red, darkhl.green, darkhl.blue );
draw_setpalette( LIGHT_HIGHLIGHT, lighthl.red, lighthl.green, lighthl.blue );
}
void
setbwpalette( void )
{
draw_setpalette( 0, 0, 0, 0 );
draw_setpalette( LABEL_COLOR, 63, 63, 63 );
draw_setpalette( BORDER_COLOR, 63, 63, 63 );
draw_setpalette( TEXT_COLOR, 63, 63, 63 );
draw_setpalette( GRAPH_COLOR, 63, 63, 63 );
draw_setpalette( DARK_HIGHLIGHT, 20, 20, 20 );
draw_setpalette( LIGHT_HIGHLIGHT, 63, 63, 63 );
}
/*
* Parse the command line
*/
void
parse_command( int argc, char *argv[], char *environ[] )
{
int i = 0;
while ( i < argc )
{
if ( argv[i][0] == '-' )
{
switch ( argv[i][1] )
{
case 'C': /* Select the sound card */
case 'c':
Soundcard = parsecardname( argv[i] + 2 );
break;
case 'S': /* Set the sampling rate (in Hz) */
case 's':
SampleRate = atol( &argv[i][2] );
break;
case 'F': /* Set the FFT length */
case 'f':
fftlen = atoi( &argv[i][2] );
break;
case 'M': /* Set the maximum value for linear display */
case 'm':
ys = atof( &argv[i][2] );
break;
case 'L': /* Set logarithmic frequency scale */
case 'l':
logfreq = 1;
break;
case '?': /* Display some help information */
case 'H':
case 'h':
puts( "-Cnumber selects the soundcard (0=SB, 1=SB16)." );
puts( "-Snumber sets the sampling rate." );
puts( "-Fnumber sets the length of the FFT." );
puts( "-Mnumber sets the scale maximum." );
puts( "-L sets a logarithmic scale for the frequency axis." );
puts( "-? or -H displays this message." );
exit( 0 );
default:
printf( "Ignoring unrecognized switch: %s\n", argv[i] );
puts( "Press <ENTER> to continue..." );
getch( );
}
}
else
printf( "Ignoring unrecognized parameter: %s (use -h for help)\n", argv[i] );
i++;
}
/*
* Watch for bad choice of log scales
*/
if ( ys > 2.0 )
ys = 2.0;
if ( ys < 0.01 )
ys = 0.01;
if ( SampleRate > 88200L )
SampleRate = 88200L;
if ( SampleRate < 5000L )
SampleRate = 5000L;
if ( fftlen < 8 )
fftlen = 8;
if ( fftlen > MAX_LEN )
fftlen = MAX_LEN;
/* Convert fftlen to a power of 2 */
for ( i = 0; fftlen > 1; fftlen >>= 1, i++ );
if ( i )
fftlen <<= i;
/* Set up the soundcard function pointers */
#ifdef SC_SB8
if ( Soundcard == SC_SB8 )
init_sb8( environ );
#endif
#ifdef SC_SB16
if ( Soundcard == SC_SB16 )
init_sb16( environ );
#endif
if ( reset_soundcard == NULL )
{
puts( "Error: Invalid soundcard selection" );
puts( "Valid choices are:" );
#ifdef SC_SB8
printf( " %s (%d)\n", SC_SB8_NAME, SC_SB8 );
#endif
#ifdef SC_SB16
printf( " %s (%d)\n", SC_SB16_NAME, SC_SB16 );
#endif
exit( 1 );
}
}
/* Parse a sound card number or label */
int
parsecardname( char *label )
{
int i = 0, j;
char name[10];
/* Skip over any leading space */
while ( label[i] == ' ' )
i++;
/* Check for numerical values */
if ( label[i] <= '9' )
return ( label[i] - '0' );
/* Convert the string to upper case */
for ( j = 0; label[i] && ( j < 9 ); i++, j++ )
name[j] = toupper( label[i] );
name[j] = 0;
/* Check for valid string values */
#ifdef SC_SB8
if ( strncmp( name, SC_SB8_NAME, strlen( SC_SB8_NAME ) ) == 0 )
return SC_SB8;
#endif
#ifdef SC_SB16
if ( strncmp( name, SC_SB16_NAME, strlen( SC_SB16_NAME ) ) == 0 )
return SC_SB16;
#endif
return ( -1 ); /* If none match, return failure */
}
/*
* Allocate memory for and initialize the buffers
*/
void
setup_buffers( int length )
{
int i, ll;
char far *p;
for ( i = 0; i < BUFFERS; i++ )
{
if ( ( buffer[i] = aligned_malloc( length * ( sample_size / 8 ) ) ) == NULL )
{
puts( "Unable to allocate enough memory for the buffers!" );
exit( 1 );
}
/* Pre-set the buffer to all zeros */
p = buffer[i];
for ( ll = 0; ll < length * ( sample_size / 8 ); ll++ )
*p++ = 0;
flag[i] = 0;
}
if ( ( fftdata = ( short * ) malloc( length * sizeof( short ) ) ) == NULL )
{
puts( "Unable to allocate enough memory for the buffers!" );
exit( 1 );
}
if ( ( wind = ( short * ) malloc( length * sizeof( short ) ) ) == NULL )
{
puts( "Unable to allocate enough memory for the buffers!" );
exit( 1 );
}
if ( ( displayval = ( long * ) malloc( length / 2 * sizeof( long ) ) ) == NULL )
{
puts( "Unable to allocate enough memory for the buffers!" );
exit( 1 );
}
if ( ( ybase = ( long * ) malloc( length / 2 * sizeof( long ) ) ) == NULL )
{
puts( "Unable to allocate enough memory for the buffers!" );
exit( 1 );
}
/*
* Clear out the memory buffers
*/
for ( i = 0; i < ( WINDOW_RIGHT - WINDOW_LEFT + 1 ); i++ )
lasty[i] = WINDOW_BOTTOM;
}
void
compute_window_function( void )
{
int i;
/*
* Calculate FFT Windowing function
*/
for ( i = 0; i < fftlen; i++ )
{
double val;
val = 0.54 - 0.46 * cos( 2 * M_PI * i / fftlen ); /* Hamming */
wind[i] = floor( val * 32767 + 0.5 );
}
}
/*
* Do some range checking on the base and scale factors
*/
void
xrange_check( void )
{
if ( freq_scalefactor < 1.0 )
freq_scalefactor = 1.0;
if ( freq_scalefactor > 16.0 )
freq_scalefactor = 16.0;
if ( logfreq )
{
double max_base = SampleRate / 2 / exp( log( fftlen / 2 ) / freq_scalefactor );
if ( freq_base < ( double ) SampleRate / fftlen )
freq_base = ( double ) SampleRate / fftlen;
if ( freq_base > max_base )
freq_base = max_base;
}
else
{
if ( freq_base < 0 )
freq_base = 0;
if ( ( freq_base + SampleRate / ( 2 * freq_scalefactor ) ) > SampleRate / 2 )
freq_base = SampleRate / 2 - SampleRate / ( 2 * freq_scalefactor );
}
if ( logfreq )
maxfreq = freq_base * exp( log( fftlen / 2 ) / freq_scalefactor );
else
maxfreq = freq_base + ( double ) SampleRate / ( freq_scalefactor * 2.0 );
}
/*
* Set up X axis scales
*/
void
setup_xscale( void )
{
int i;
/*
* Do some range checking on the base and scale factors
*/
xrange_check( );
/*
* Initialize graph x scale (linear or logarithmic). This array points to
* the bin to be plotted on a given line.
*/
for ( i = 0; i <= ( WINDOW_RIGHT - WINDOW_LEFT + 1 ); i++ )
{
int val;
if ( logfreq )
val = floor( ( double ) fftlen * freq_base / ( double ) SampleRate
* exp( ( i - 0.45 ) / ( double ) ( WINDOW_RIGHT - WINDOW_LEFT )
* log( fftlen / 2 ) / freq_scalefactor ) + 0.5 );
else
val = floor( ( ( i - 0.45 ) / ( double ) ( WINDOW_RIGHT - WINDOW_LEFT )
* ( double ) fftlen / 2.0 / freq_scalefactor )
+ ( freq_base / ( double ) SampleRate * ( double ) fftlen ) + 0.5 );
if ( val < 0 )
val = 0;
if ( val >= fftlen / 2 )
val = fftlen / 2 - 1;
if ( i > 0 )
x2[i - 1] = val;
if ( i <= ( WINDOW_RIGHT - WINDOW_LEFT ) )
x[i] = val;
}
/* Compute the ending locations for lines holding multiple bins */
for ( i = 0; i <= ( WINDOW_RIGHT - WINDOW_LEFT ); i++ )
{
if ( x2[i] <= ( x[i] + 1 ) )
x2[i] = 0;
}
/*
* If lines are repeated on the screen, flag this so that we don't have to
* recompute the y values.
*/
for ( i = ( WINDOW_RIGHT - WINDOW_LEFT ); i > 0; i-- )
{
if ( x[i] == x[i - 1] )
{
x[i] = -1;
x2[i] = 0;
}
}
setup_linscales( );
frequency_scale( );
}
/*
* Set up linear amplitude scale factors
*/
void
setup_linscales( void )
{
int i;
double scale;
/*
* Compute the (linear) y scale factor.
*/
scale = ( WINDOW_BOTTOM - WINDOW_TOP ) / ( ys * 32768.0 );
shift = 4; /* Display data has an extra factor of 16 for
* better resolution */
{
/*
* Make maximum use of available bits (use only 12 bits--other 4 used for
* higher resolution in the data)
*/
while ( scale < 4096 )
{
scale *= 2;
shift++;
}
scale = floor( scale + 0.5 );
for ( i = 0; i < ( WINDOW_RIGHT - WINDOW_LEFT + 1 ); i++ )
{
if ( x[i] == -1 )
yscale[i] = 0;
else
yscale[i] = scale;
}
}
shiftscale = 1 / pow( 2, shift );
}
void
frequency_scale( void )
{
int i;
double step, freq;
char text[20];
/*
* Put up the frequency scale.
*/
draw_bar( WINDOW_LEFT - 10, WINDOW_BOTTOM + 3, WINDOW_RIGHT + 10, 469, 0 );
draw_fontcolor( LABEL_COLOR );
if ( logfreq )
step = log( fftlen / 2 ) / ( 32.0 * freq_scalefactor );
else
step = ( double ) SampleRate / ( 64.0 * freq_scalefactor );
for ( i = 0; i <= 32; i++ )
{
int x = WINDOW_LEFT + ( ( double ) i * ( WINDOW_RIGHT - WINDOW_LEFT ) / 32.0 );
int y = WINDOW_BOTTOM + 3;
draw_line( x, y, x, y + 3, BORDER_COLOR );
if ( logfreq )
freq = freq_base * exp( step * i );
else
freq = freq_base + i * step;
if ( freq < 0 )
freq = 0;
sprintf( text, "%.0f", freq );
draw_text_vertical( x - _font_width / 2, y + 6, text );
}
draw_fontcolor( TEXT_COLOR );
}
void
amplitude_scale( void )
{
int i;
char text[20];
draw_bar( 0, WINDOW_TOP - 10, WINDOW_LEFT - 3, WINDOW_BOTTOM + 7, 0 );
draw_bar( WINDOW_RIGHT + 3, WINDOW_TOP - 10, 639, WINDOW_BOTTOM + 7, 0 );
draw_fontcolor( LABEL_COLOR );
/*
* Put up the amplitude scale
*/
{
double scale = ( double ) ( ys * 32768.0 ) / ( double ) ( WINDOW_BOTTOM - WINDOW_TOP );
for ( i = 0; i <= 10; i++ )
{
int x = WINDOW_LEFT - 3;
int y = WINDOW_BOTTOM - i * ys * 3276.8 / scale;
draw_line( x, y, x - 3, y, BORDER_COLOR );
if ( ys > 0.095 )
sprintf( text, "%4.2f", ( float ) i * ys * 0.1 );
else
sprintf( text, "%5.3f", ( float ) i * ys * 0.1 );
draw_text_right( x - 6, y - _font_height / 2, text );
draw_line( WINDOW_RIGHT + 3, y, WINDOW_RIGHT + 6, y, BORDER_COLOR );
draw_text_left( WINDOW_RIGHT + 9, y - _font_height / 2, text );
}
}
draw_fontcolor( TEXT_COLOR );
}
void
log_state( void )
{
draw_bar( 24, 24, 112, 36, 0 );
draw_fontcolor( LABEL_COLOR );
if ( log_mode )
draw_text_left( 24, 24, "Logging ON" );
else
draw_text_left( 24, 24, "Logging OFF" );
}
void
update_header( void )
{
char ach[100];
draw_bar( 0, 0, 639, WINDOW_TOP - 10, 0 );
draw_bar( WINDOW_LEFT - 5, WINDOW_TOP - 10, WINDOW_RIGHT + 5, WINDOW_TOP - 4, 0 );
draw_fontcolor( TEXT_COLOR );
sprintf( ach, "Sampling rate: %ld Hz", SampleRate );
draw_text_centered( SRX, SRY, ach );
sprintf( ach, "FFT Length: %d points", fftlen );
draw_text_centered( FLX, FLY, ach );
sprintf( ach, "Frequency resolution: %.4g Hz", ( double ) SampleRate / ( double ) fftlen );
draw_text_centered( FRX, FRY, ach );
if ( dtmf_nr[0] != 0 )
{
dtmf_nr[64] = 0; /* maximum 64 DTMF numbers */
draw_fontcolor( GRAPH_COLOR );
draw_text_left( WINDOW_LEFT, MGY - 36, "Number stored in memory: " );
draw_text_left( WINDOW_LEFT, MGY - 24, dtmf_nr );
draw_fontcolor( TEXT_COLOR );
}
if ( gen_ctcss != CTCSS_MAX )
{
draw_fontcolor( GRAPH_COLOR );
sprintf( ach, "CTCSS %.1f Hz", f_ctcss[gen_ctcss] );
draw_text_right( WINDOW_RIGHT, 2, ach );
sprintf( ach, "Error %.2f Hz", err_ctcss );
draw_text_right( WINDOW_RIGHT, 14, ach );
draw_fontcolor( TEXT_COLOR );
}
if ( dtmf_mode )
{
draw_fontcolor( BORDER_COLOR );
draw_text_left( 24, 0, "DTMF mode" );
draw_fontcolor( LABEL_COLOR );
draw_text_left( 24, 60, "DTMF numbers:" );
draw_threshold_level( TEXT_COLOR );
log_state( );
}
if ( ctcss_mode )
{
draw_fontcolor( BORDER_COLOR );
draw_text_left( 24, 0, "CTCSS mode" );
draw_fontcolor( GRAPH_COLOR );
draw_text_left( 24, 60, "CTCSS Freq:" );
draw_threshold_level( TEXT_COLOR );
log_state( );
}
if ( mixers )
{
sprintf( ach, "Mic:%3d Ext:%3d CD:%3d", mic_level, ext_level, int_level );
draw_text_left( LVX, LVY, ach );
}
}
void
show_help( void )
{
draw_bar( 0, 0, 639, WINDOW_TOP - 10, 0 );
draw_bar( WINDOW_LEFT - 5, WINDOW_TOP - 10, WINDOW_RIGHT + 5, WINDOW_TOP - 4, 0 );
if ( help_mode == 1 )
{
draw_text_left( 20, 2, " 'D' Toggle DTMF mode" );
draw_text_left( 20, 14, " 'T' Toggle CTCSS mode" );
draw_text_left( 20, 26, " 'I' Input a DTMF number" );
draw_text_left( 20, 38, " <TAB> Compose the stored DTMF #" );
draw_text_left( 20, 50, " 'A' Generate a subAudible tone" );
draw_text_left( 20, 62, " 'G' Toggle loGfile (dtmf & ctcss)" );
draw_text_left( 20, 74, "<SPACE> Freeze display" );
draw_text_left( 340, 2, " 'L' Toggle Log/Linear frequency" );
draw_text_left( 340, 14, " 'R' Change the sampling Rate" );
draw_text_left( 340, 26, " 'F' Change the FFT length" );
draw_text_left( 340, 38, " 'P' Toggle Peak display mode" );
draw_text_left( 340, 50, " 'S' Save State to INI file" );
draw_text_left( 340, 62, "'H','?' Toggle this Help screen" );
draw_text_left( 340, 74, "'Q','E' Exit from the program" );
}
else
{
draw_text_left( 100, 2, "<Left/Right> Shift the frequency axis" );
draw_text_left( 100, 14, " '<','>' Expand/contract the frequency axis" );
draw_text_left( 100, 26, " <Up/Down> Change the amplitude scale" );
draw_text_left( 100, 38, " <PgUp/PgDn> Threshold level in DTMF & CTCSS mode" );
draw_text_left( 100, 50, "<Home>/<End> Move to the minimum/maximum freqency" );
if ( mixers )
{
draw_text_left( 60, 62, "'C' Toggle B&W/color display" );
draw_text_left( 340, 62, "'V' Refresh display" );
draw_text_left( 60, 74, "(),[],{} Adjust mic,ext,CD input level" );
}
else
{
draw_text_left( 60, 62, "'C' Toggle B&W/color display" );
draw_text_left( 340, 62, "'V' Refresh display" );
}
}
}